/*
 * @(#)RawStreamParser.java	1.13 02/08/21
 *
 * Copyright (c) 1996-2002 Sun Microsystems, Inc.  All rights reserved.
 */

package com.sun.media.parser;

import java.io.IOException;

import javax.media.Buffer;
import javax.media.Demultiplexer;
import javax.media.Format;
import javax.media.IncompatibleSourceException;
import javax.media.Time;
import javax.media.Track;
import javax.media.TrackListener;
import javax.media.protocol.DataSource;
import javax.media.protocol.PushDataSource;
import javax.media.protocol.PushSourceStream;
import javax.media.protocol.SourceStream;
import javax.media.protocol.SourceTransferHandler;

import com.sun.media.CircularBuffer;


public class RawStreamParser extends RawParser {

    protected SourceStream[] streams;
    protected Track[] tracks = null;

    static final String NAME = "Raw stream parser";

    public String getName() {
	return NAME;
    }

    public RawStreamParser() {
    }

    public void setSource(DataSource source)
	throws IOException, IncompatibleSourceException {

	if (!(source instanceof PushDataSource)) {
	    throw new IncompatibleSourceException("DataSource not supported: " + source);
	} else {
	    streams = ((PushDataSource) source).getStreams();
	}


	if ( streams == null) {
	    throw new IOException("Got a null stream from the DataSource");
	}

	if (streams.length == 0) {
	    throw new IOException("Got a empty stream array from the DataSource");
	}

	if (!supports(streams))
	    throw new IncompatibleSourceException("DataSource not supported: " + source);

	this.source = source;
	this.streams = streams;
	
	// System.out.println("content length is " + streams[0].getContentLength());
    }

    /**
     * Override this if the Parser has additional requirements
     * from the PushSourceStream
     */
    protected boolean supports(SourceStream[] streams) {
	return ( (streams[0] != null) &&
		 (streams[0] instanceof PushSourceStream) );
    }

    public Track [] getTracks() {
	return tracks;
    }

    /**
     * Opens the plug-in software or hardware component and acquires
     * necessary resources. If all the needed resources could not be
     * acquired, it throws a ResourceUnavailableException. Data should not
     * be passed into the plug-in without first calling this method.
     */
    public void open() {
	if (tracks != null)
	    return;
	tracks = new Track[streams.length];
	for (int i = 0; i < streams.length; i++) {
	    tracks[i] = new FrameTrack(this, (PushSourceStream)streams[i], 5);
	}
    }

    /**
     * Closes the plug-in component and releases resources. No more data
     * will be accepted by the plug-in after a call to this method. The
     * plug-in can be reinstated after being closed by calling
     * <code>open</code>.
     */
    public void close() {
	if (source != null) {
	    try {
		source.stop();
		// stop each of the tracks, so that readFrame() can be
		// released.
		for (int i = 0; i < tracks.length; i++)
		    ((FrameTrack)tracks[i]).stop();
		
		source.disconnect();
	    } catch (IOException e) {
		// Internal error?
	    }
	    source = null;
	}
    }

    /**
     * Start the parser.
     */
    public void start() throws IOException {
	source.start();
	for (int i = 0; i < tracks.length; i++)
	    ((FrameTrack)tracks[i]).start();
    }

    /**
     * Stop the parser.
     */
    public void stop() {
	try {
	    source.stop();
	    // stop each of the tracks, so that readFrame can be released
	    for (int i = 0; i < tracks.length; i++)
		((FrameTrack)tracks[i]).stop();
	} catch (IOException e) {
	    // Internal errors?
	}
    }


    ////////////////////////
    //
    // Inner class
    ////////////////////////

    class FrameTrack implements Track, SourceTransferHandler {
	Demultiplexer parser;
	PushSourceStream pss;
	boolean enabled = true;
	CircularBuffer bufferQ;
	Format format = null;
	TrackListener listener;
	Integer stateReq = new Integer(0);
	boolean stopped = true;

	
	public FrameTrack(Demultiplexer parser, PushSourceStream pss, int numOfBufs) {
	    this.pss = pss;
	    pss.setTransferHandler(this);
	    bufferQ = new CircularBuffer(numOfBufs);
	}

	public Format getFormat() {
	    return format;
	}

	public void setEnabled(boolean t) {
	    if (t)
		pss.setTransferHandler(this);
	    else
		pss.setTransferHandler(null);
	    enabled = t;
	}

	public boolean isEnabled() {
	    return enabled;
	}

	public Time getDuration() {
	    return parser.getDuration(); 
	}

	public Time getStartTime() {
	    return new Time(0);
	}

	public void setTrackListener(TrackListener l) {
	    listener = l;
	}

	public void readFrame(Buffer buffer) {

		
	    // Retrieve a filled buffer.
	    Buffer filled;
	    synchronized (stateReq){
	    	if (stopped){
		    buffer.setDiscard(true);
		    buffer.setFormat(format);
		    return;
		}
	    }
	    synchronized (bufferQ) {
	    	while (!bufferQ.canRead()) {
		    try {
			bufferQ.wait();
			synchronized(stateReq){
			    if (stopped){
				buffer.setDiscard(true);
				buffer.setFormat(format);
				return;
			    }
			}
		    } catch (Exception e) {}
		}
		filled = bufferQ.read();
		bufferQ.notifyAll();
	    }

	    // exchange the buffers.
	    byte data[] = (byte [])filled.getData();;
	    filled.setData(buffer.getData());
	    buffer.setData(data);
	    buffer.setLength(filled.getLength());
	    buffer.setFormat(format);
	    buffer.setTimeStamp(Buffer.TIME_UNKNOWN);
	    

	    synchronized (bufferQ) {
		bufferQ.readReport();
		bufferQ.notifyAll();
	    }

	}
	public void stop(){
	    // we basically need to ensure that readFrame will return
	    // immediately.and also make sure that if it is called in
	    // the stopped state, it returns w/o blocking.
	    synchronized(stateReq){
		stopped = true;
	    }
	    synchronized (bufferQ){
		bufferQ.notifyAll();
	    }
	}
	public void start(){
	    // we need to ensure that readFrame is returned to its
	    // original state and does not return w/o blocking.
	    synchronized(stateReq){
		stopped = false;
	    }
	    synchronized (bufferQ){
		bufferQ.notifyAll();
	    }
	}

	public int mapTimeToFrame(Time t) {
	    return -1;
	}

	public Time mapFrameToTime(int frameNumber) {
	    return new Time(0);
	}

	public void transferData(PushSourceStream pss) {

	    // Retrieve an empty buffer for the PSS to write into.
	    Buffer buffer;
	    synchronized (bufferQ) {
		while (!bufferQ.canWrite()) {
		    try {
			bufferQ.wait();
		    } catch (Exception e) {}
		}
		buffer = bufferQ.getEmptyBuffer();
		bufferQ.notifyAll();
	    }

	    int size = pss.getMinimumTransferSize();
	    byte data[];
	    if ((data = (byte [])buffer.getData()) == null ||
		data.length < size) {
		data = new byte[size];
		buffer.setData(data);
	    }

	    try {
		int len = pss.read(data, 0, size);
		buffer.setLength(len);
	    } catch (IOException e) {
		buffer.setDiscard(true);
	    }

	    // Put the filled buffer back to the queue for consumption.
	    synchronized (bufferQ) {
		bufferQ.writeReport();
		bufferQ.notifyAll();
	    }
	}
    }
}

